/*
 * Copyright 2011 Clean GWT
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.cleangwt.bootstrap.client.ui;

import com.cleangwt.bootstrap.client.style.Styles;
import com.cleangwt.uibinder.client.TemplateSupport;
import com.cleangwt.user.client.Elements;
import com.cleangwt.user.client.activity.TabDisplay;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
import com.google.gwt.event.logical.shared.HasBeforeSelectionHandlers;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * The quick, dynamic tab functionality to transition through panes of local content, even via
 * dropdown menus.
 * 
 * @author Jess
 * @version 1.0
 * @since 1.0
 * @se http://getbootstrap.com/javascript/#tabs
 */
public class TabPanel extends ComplexPanel implements TabDisplay, HasSelectionHandlers<Integer>,
    HasBeforeSelectionHandlers<Integer> {

  /**
   * The tab dropdown builder.
   */
  public class TabDropdownBuilder extends DropdownBuilder {
    private TabPanel tabPanel;

    /**
     * Creates a tab dropdown builder with the given menu element.
     * 
     * @param menu the menu element to build
     */
    private TabDropdownBuilder(TabPanel tabPanel, Element dropdown) {
      super(dropdown);
      this.tabPanel = tabPanel;
    }

    public TabDropdownBuilder addTab(String html, Widget widget) {
      tabPanel.addTab(this.menu, html, widget);
      return this;
    }
  }

  /**
   * The tab placement.
   */
  public enum TabPlacement {
    // currently not support other placement
    TOP("tabs-top");// ,LEFT("tabbable tabs-left"),RIGHT("tabbable tabs-right"),BOTTOM("tabs-below");
    String style;

    private TabPlacement(String style) {
      this.style = style;
    }
  }

  /**
   * Creates a button that wraps an existing &lt;div&gt; element of template.
   * 
   * @param template the template
   * @param element the element to be wrapped
   */
  public static TabPanel wrap(TemplateSupport template, Element element) {
    TabPanel widget = new TabPanel(element);
    template.wrap(widget);
    return widget;
  }

  private static final String CONTENT_STYLE = "tab-content";

  // /**
  // * Creates a inline form panel that wraps an existing &lt;div&gt; element
  // of
  // * template.
  // *
  // * @param template
  // * the template
  // * @param element
  // * the element to be wrapped
  // */
  // public static TabPanel wrap(TemplateSupport template, Element element) {
  // TabPanel widget = new TabPanel(element);
  // template.wrap(widget);
  // return widget;
  // }

  private Element tabs;
  private Element tabContent;

  private boolean beforeHandlerInitialized;
  private boolean selectionHandlerInitialized;

  // /**
  // * This constructor may be used by subclasses to explicitly use an
  // existing
  // * element. This element must be a &lt;div&gt; element.
  // *
  // * @param element
  // * the element to be used
  // */
  // protected TabPanel(Element element) {
  // setElement(element);
  // }

  public TabPanel() {
    this(TabPlacement.TOP);
  }

  /**
   * Creates a tab panel with the given tab placement.
   * 
   * @param tabPlacement the tab placement
   */
  public TabPanel(TabPlacement tabPlacement) {
    this(tabPlacement, false);
  }

  /**
   * Creates a tab panel with the given tab placement and 'justified' option.
   * 
   * @param tabPlacement the tab placement
   * @param justified whether to add justified style to the tab container
   */
  public TabPanel(TabPlacement tabPlacement, boolean justified) {
    setElement(Document.get().createDivElement());
    tabs = Document.get().createULElement();
    tabContent = Document.get().createDivElement();

    // this.getElement().addClassName(tabPlacement.style);
    tabs.setClassName(Navigators.NAV_STYLE);
    tabs.addClassName(Navigators.TABS_STYLE);
    if (justified) {
      tabs.addClassName(Navigators.JUTIFIED_STYLE);
    }
    tabContent.setClassName(CONTENT_STYLE);
    // if(tabPlacement == TabPlacement.BOTTOM){
    // this.getElement().appendChild(tabContent);
    // this.getElement().appendChild(tabs);
    // }else{
    this.getElement().appendChild(tabs);
    this.getElement().appendChild(tabContent);
    // }
  }

  /**
   * This constructor may be used by subclasses to explicitly use an existing element. This element
   * must be a &lt;div&gt; element.
   * 
   * @param element the element to be used
   */
  protected TabPanel(Element element) {
    this.setElement(element);
    tabs = element.getFirstChildElement();
    tabContent = tabs.getNextSiblingElement();
  }

  @Override
  public HandlerRegistration addBeforeSelectionHandler(BeforeSelectionHandler<Integer> handler) {
    if (!beforeHandlerInitialized) {
      beforeHandlerInitialized = true;
      initializeBeforeHandler();
    }
    return this.addHandler(handler, BeforeSelectionEvent.getType());
  }

  /**
   * Adds a tab with the given text and content widget.
   * 
   * @param tabText the tab text
   * @param w the content widget
   */
  public void add(Widget w, String tabText) {
    this.addTab(tabs, tabText, w);
  }

  /**
   * Adds a dropdown with the given toggle text and returns the {@link DropdownBuilder} to build it.
   * 
   * @param toggleText the text for toggle element
   * @return the dropdown builder to build a complete list item dropdown
   */
  public TabDropdownBuilder addDropdown(String toggleText) {
    Element dropdown = DropdownBuilder.createNavDropdown(toggleText);
    this.getElement().appendChild(dropdown);
    this.tabs.appendChild(dropdown);
    return new TabDropdownBuilder(this, dropdown);
  }

  @Override
  public HandlerRegistration addSelectionHandler(SelectionHandler<Integer> handler) {
    if (!selectionHandlerInitialized) {
      selectionHandlerInitialized = true;
      initializeSelectionHandler();
    }
    return this.addHandler(handler, SelectionEvent.getType());
  }

  /**
   * Adds a tab to the tab containers, it can be also a sub menu of dropdown.
   * 
   * @param tabsOrDropdownMenu the tab container or dropdown menu
   * @param tab the tab text
   * @param content the content widget
   */
  private void addTab(Element tabsOrDropdownMenu, String tab, Widget content) {
    Element li = Document.get().createLIElement();
    Element a = Document.get().createAnchorElement();
    Element tabPane = Document.get().createDivElement();
    String tabId = Document.get().createUniqueId();

    tabsOrDropdownMenu.appendChild(li);
    tabContent.appendChild(tabPane);
    this.add(content, tabPane);

    li.appendChild(a);

    a.setAttribute("href", "#" + tabId);
    a.setAttribute(DataModel.DATA_TOGGLE, "tab");
    a.setInnerHTML(tab);
    tabPane.setId(tabId);
    tabPane.setClassName("tab-pane fade");

    // if the given widget is a lazy panel, enure the content widget of lazy
    // panel is created on tab be clickd.
    if (beforeHandlerInitialized) {
      this.initializeBeforeHandler(a);
    }
    if (selectionHandlerInitialized) {
      this.initializeSelectionHandler(a);
    }
  }

  @Override
  public void clear() {
    while (getWidgetCount() > 0) {
      remove(getWidget(0));
    }
  }

  private Widget findContentWidget(Element e) {
    for (int i = 0; i < this.getWidgetCount(); i++) {
      Widget w = this.getWidget(i);
      if (e == w.getElement()) {
        return w;
      }
    }
    return null;
  }

  private void fireBeforeEventFromShow(NativeEvent e) {
    Element target = e.getEventTarget().cast();
    if (Elements.hasClass(Styles.DISABLED, target.getParentElement())) {
      e.preventDefault();
      return;
    }
    String id = target.getAttribute("href").substring(1);
    BeforeSelectionEvent<?> beforeSelectionEvent = BeforeSelectionEvent.fire(this, getTabIndex(id));
    if (beforeSelectionEvent != null && beforeSelectionEvent.isCanceled()) {
      e.preventDefault();
    }
  }

  private void fireSelectionEventFromShown(Element target) {
    String id = target.getAttribute("href").substring(1);
    SelectionEvent.fire(this, getTabIndex(id));
  }

  @Override
  public Widget getDeck(int index) {
    Element tabPane = tabContent.getChild(index).cast();
    return findContentWidget(tabPane.getFirstChildElement());
  }

  private int getTabIndex(String id) {
    for (int i = 0; i < this.tabContent.getChildCount(); i++) {
      Element pane = tabContent.getChild(i).cast();
      if (pane.getId().equals(id)) {
        return i;
      }
    }
    return -1;
  }

  @Override
  public int getVisibleDeck() {
    for (int i = 0; i < this.tabContent.getChildCount(); i++) {
      Element pane = tabContent.getChild(i).cast();
      if (Elements.hasClass(Styles.ACTIVE, pane)) {
        return i;
      }
    }
    return -1;
  }

  private native void initializeBeforeHandler() /*-{
		var the = this;
		$wnd
				.jQuery(
						this.@com.cleangwt.bootstrap.client.ui.TabPanel::getElement()())
				.find('a[data-toggle="tab"]')
				.on(
						'show.bs.tab',
						function(e) {
							the.@com.cleangwt.bootstrap.client.ui.TabPanel::fireBeforeEventFromShow(Lcom/google/gwt/dom/client/NativeEvent;)(e);
						})
  }-*/;

  private native void initializeBeforeHandler(Element e) /*-{
		var the = this;
		$wnd
				.jQuery(e)
				.on(
						'show.bs.tab',
						function(e) {
							the.@com.cleangwt.bootstrap.client.ui.TabPanel::fireBeforeEventFromShow(Lcom/google/gwt/dom/client/NativeEvent;)(e);
						})
  }-*/;

  private native void initializeSelectionHandler() /*-{
		var the = this;
		$wnd
				.jQuery(
						this.@com.cleangwt.bootstrap.client.ui.TabPanel::getElement()())
				.find('a[data-toggle="tab"]')
				.on(
						'shown.bs.tab',
						function(e) {
							the.@com.cleangwt.bootstrap.client.ui.TabPanel::fireSelectionEventFromShown(Lcom/google/gwt/dom/client/Element;)(e.target);
						})
  }-*/;

  private native void initializeSelectionHandler(Element e) /*-{
		var the = this;
		$wnd
				.jQuery(e)
				.on(
						'shown.bs.tab',
						function(e) {
							the.@com.cleangwt.bootstrap.client.ui.TabPanel::fireSelectionEventFromShown(Lcom/google/gwt/dom/client/Element;)(e.target);
						})
  }-*/;

  @Override
  public boolean remove(Widget w) {
    boolean removed = super.remove(w);
    if (removed) {
      tabs.getChild(this.getWidgetIndex(w)).removeFromParent();
    }
    return removed;
  }

  @Override
  public void setVisibleDeck(int index) {
    assert !Elements.hasClass(Styles.DISABLED, (Element) this.tabs.getChild(index)) : "This card is disabled.";

    // if (!this.isAttached()) {
    for (int i = 0; i < this.tabs.getChildCount(); i++) {
      Element tab = tabs.getChild(i).cast();
      Element content = tabContent.getChild(i).cast();
      if (i == index) {
        tab.addClassName(Styles.ACTIVE);
        content.addClassName(Styles.ACTIVE);
        if (Elements.hasClass(Styles.FADE, content)) {
          content.addClassName(Styles.IN);
        }
      } else {
        tab.removeClassName(Styles.ACTIVE);
        content.removeClassName(Styles.ACTIVE);
        if (Elements.hasClass(Styles.FADE, content)) {
          content.removeClassName(Styles.IN);
        }
      }
    }
    // }
    // else{
    // Element card = tabContent.getChild(index).cast();
    // showWidget(card.getId());
    // }
  }

  /**
   * Sets the tab enabled of the given index.
   * 
   * @param index the tab index
   * @param enabled the enabled
   */
  public void setEnabled(int index, boolean enabled) {
    Element tab = tabs.getChild(index).cast();
    if (enabled) {
      tab.removeClassName(Styles.DISABLED);
    } else {
      tab.addClassName(Styles.DISABLED);
    }
  }

  private native void showWidget(String id) /*-{
		$wnd
				.jQuery(
						this.@com.cleangwt.bootstrap.client.ui.TabPanel::getElement()())
				.find('a[href="#' + id + '"]').tab('show');
  }-*/;

}